/*
 * Copyright (C) 2014 luke.
 * 
 * This file is part of Silvie.
 * 
 * Silvie is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Silvie is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Silvie.  If not, see <http://www.gnu.org/licenses/>.
 */

package silvie.gui;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Panel;
import java.awt.RenderingHints;
import java.awt.ScrollPane;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.Arrays;

import utils.CanvasDrag;
import utils.DoubleBufferedCanvas;

/**
 * Encapsulates an AWT Frame that can be used to display a resource's preview.
 * 
 * <p>
 * This class uses AWT components only because the Canvas3D implementation from
 * Java3D does not work very well with lightweight components.
 * 
 * @author luke
 */
public class Preview {
	private static final String FRAME_TITLE = "Preview";
	// A bit more than 640x480 because of AWT differences across OSes
	private static final int SCROLLPANE_PREFERRED_WIDTH = 644;
	private static final int SCROLLPANE_PREFERRED_HEIGHT = 484;
	// @formatter:off
	private static final String[] DEFAULT_TEXT = {
		"Silvie - The Silver Viewer",
		"revision 8 by luke - nerd@sfr.fr",
		" ",
		"Ctrl+O:  " + SilvieGUI.OPEN_DIALOG_MESSAGE,
		"Ctrl+S:      " + SilvieGUI.SAVE_DIALOG_MESSAGE,
		"<ESC> :           Show this help screen"
	};
	// @formatter:on
	private static final String EXCEPTION_NO_MESSAGE_TEXT = "No message, see stack trace in console !";
	private static final Color BACKGROUND_COLOR = Color.BLACK;
	private static final Color TEXT_COLOR = Color.WHITE;
	private static final Color EXCEPTION_COLOR = Color.RED;
	private static final Font FONT = new Font(Font.MONOSPACED, Font.PLAIN, 12);
	private static final FontMetrics FONT_METRICS = new Canvas().getFontMetrics(FONT);
	private static final int SCROLL_UNIT_INCREMENT = 10;
	private Frame previewFrame;
	private ScrollPane scrollPane;
	private Panel panel;

	/**
	 * Preview constructor. It creates the AWT components and displays the
	 * default text.
	 */
	public Preview() {
		this.previewFrame = new Frame(FRAME_TITLE);
		this.previewFrame.setIconImage(SilvieGUI.getCachedIcon(SilvieGUI.SILVER_ICON_PATH).getImage());
		this.previewFrame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				Preview.this.previewFrame.setVisible(false);
			}
		});
		this.scrollPane = new ScrollPane();
		this.scrollPane.getVAdjustable().setUnitIncrement(SCROLL_UNIT_INCREMENT);
		this.scrollPane.getHAdjustable().setUnitIncrement(SCROLL_UNIT_INCREMENT);
		this.scrollPane.setPreferredSize(new Dimension(SCROLLPANE_PREFERRED_WIDTH, SCROLLPANE_PREFERRED_HEIGHT));
		this.panel = new Panel();
		this.panel.setBackground(BACKGROUND_COLOR);
		// GridBagLayout centers Canvas components by default
		this.panel.setLayout(new GridBagLayout());
		this.scrollPane.add(this.panel);
		this.previewFrame.add(this.scrollPane);
		displayDefaultText();
		this.previewFrame.pack();
	}

	public Frame getFrame() {
		return this.previewFrame;
	}

	/**
	 * Displays the default text on the preview Canvas.
	 */
	public void displayDefaultText() {
		setCanvas(TEXT_COLOR, DEFAULT_TEXT);
	}

	/**
	 * Replaces the current preview Canvas with {@code canvas}.
	 * 
	 * @param canvas the new Canvas to display.
	 * @param makeVisible true to make the frame visible if it wasn't already,
	 *        false otherwise.
	 */
	public void setCanvas(Canvas canvas, boolean makeVisible) {
		this.panel.removeAll();
		this.panel.add(canvas);
		this.scrollPane.setScrollPosition(0, 0);
		new CanvasDrag(this.scrollPane, canvas);
		this.scrollPane.validate();
		if (makeVisible && !this.previewFrame.isVisible()) {
			this.previewFrame.setVisible(true);
		}
	}

	/**
	 * Convenience method for replacing the current preview Canvas with a new
	 * Canvas and drawing {@code textLines} on it.
	 * 
	 * @param color the color to use for the text.
	 * @param textLines the text line(s) to draw.
	 */
	public void setCanvas(Color color, String... textLines) {
		int largestWidth = Arrays.stream(textLines).map(s -> FONT_METRICS.stringWidth(s)).max(Integer::compare).get();
		int lineHeight = FONT_METRICS.getHeight();
		int textHeight = textLines.length * lineHeight;
		Canvas canvas = new DoubleBufferedCanvas() {
			private static final long serialVersionUID = 1L;

			@Override
			public Image getOffscreenImage() {
				int width = getWidth();
				int height = getHeight();
				Image image = new BufferedImage(largestWidth, textHeight, BufferedImage.TYPE_INT_RGB);
				Graphics imageGraphics = image.getGraphics();
				RenderingHints hints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
				// Enable font anti-aliasing on Linux
				((Graphics2D) imageGraphics).setRenderingHints(hints);
				imageGraphics.setColor(BACKGROUND_COLOR);
				imageGraphics.fillRect(0, 0, largestWidth, textHeight);
				imageGraphics.setColor(color);
				imageGraphics.setFont(FONT);
				for (int i = 0; i < textLines.length; i++) {
					int textWidth = FONT_METRICS.stringWidth(textLines[i]);
					imageGraphics.drawString(textLines[i], (width - textWidth) / 2, (height + textHeight) / 2 - FONT_METRICS.getDescent() - textHeight + (i + 1) * lineHeight);
				}
				return image;
			}

			@Override
			public Dimension getPreferredSize() {
				return new Dimension(largestWidth, textHeight);
			}
		};
		setCanvas(canvas, false);
	}

	/**
	 * Convenience method for replacing the current preview Canvas with a new
	 * Canvas and drawing {@code throwable}'s message on it.
	 * 
	 * @param throwable the Throwable whose message to draw.
	 */
	public void setCanvas(Throwable throwable) {
		String message = throwable.getLocalizedMessage();
		String text = throwable.getClass().getName() + ": " + (message != null ? message : EXCEPTION_NO_MESSAGE_TEXT);
		setCanvas(EXCEPTION_COLOR, text);
	}
}
